package stone.ast;

import java.util.List;

import stone.StoneException;
import stone.StoneObject;
import static stone.StoneObject.AccessException;
import ASTInterpreter.Environment;
import stone.Symbols;
import stone.OptStoneObject;

public class BinaryExpr extends ASTList {

	public BinaryExpr(List<ASTree> list) {
		super(list);
	}

	public ASTree left() {
		return child(0);
	}
	
	public ASTree right() {
		return child(2);
	}
	
	public String operator() {
		return ((ASTLeaf)child(1)).token.getText();
	}
	
	@Override
	public Object eval(Environment env) {
		String op = operator();
		if ("=".equals(op)) {
			Object right = right().eval(env);
			return computeAssign(env, right);
		}
		else {
			Object left = left().eval(env);
			Object right = right().eval(env);
			return computeOp(left, op, right);
		}
	}
	
	
	protected Object computeAssign(Environment env, Object rvalue) {
		ASTree l = left();
		if (l instanceof PrimaryExpr) {
			PrimaryExpr p = (PrimaryExpr)l;
			if (p.hasPostfix(0) && p.postfix(0) instanceof Dot) {
				Object t = ((PrimaryExpr)l).evalSubExpr(env, 1);
				if (t instanceof StoneObject) {
					return setField((StoneObject)t, (Dot)p.postfix(0), rvalue);
				}
				else if (t instanceof OptStoneObject) {
					return setField((OptStoneObject)t, (Dot)p.postfix(0), rvalue);
				}
			}
			if (p.hasPostfix(0) && p.postfix(0) instanceof ArrayRef) {
				Object a = ((PrimaryExpr) l).evalSubExpr(env, 1);
				if (a instanceof Object[]) {
					ArrayRef aref = (ArrayRef)p.postfix(0);
					Object index = aref.index().eval(env);
					if (index instanceof Integer) {
						((Object[])a)[(Integer)index] = rvalue;
						return rvalue;
					}
				}
				throw new StoneException("bad array access", this);
			}
		}
		if (l instanceof Name) {
			// env.put(((Name)l).name(), rvalue);
			((Name)l).evalForAssign(env, rvalue);
			return rvalue;
		}
		else {
			throw new StoneException("bad assignment", this);
		}
	}


	
	
	protected Object computeOp(Object left, String op, Object right) {
		if (left instanceof Integer && right instanceof Integer) {
			return computeNumber((Integer)left, op, (Integer)right);
		}
		else {
			if (op.equals("+")) {
				return String.valueOf(left) + String.valueOf(right);
			}
			else if (op.equals("==")) {
				if (left == null) {
					return right == null ? TRUE : FALSE;
				}
				else {
					return left.equals(right) ? TRUE : FALSE;
				}
			}
			else {
				throw new StoneException("bad type", this);
			}
		}
	}
	
	protected Object computeNumber(Integer left, String op, Integer right) {
		int a = left.intValue();
		int b = right.intValue();
		if (op.equals("+")) {
			return a + b;
		}
		else if(op.equals("-")) {
			return a - b;
		}
		else if (op.equals("*")) {
			return a * b;
		}
		else if (op.equals("/")) {
			return a / b;
		}
		else if (op.equals("%")) {
			return a % b;
		}
		else if (op.equals("==")) {
			return a == b ? TRUE : FALSE;
		}
		else if (op.equals(">")) {
			return a > b ? TRUE : FALSE;
		}
		else if (op.equals("<")) {
			return a < b ? TRUE : FALSE;
		}
		else {
			throw new StoneException("bad operator", this);
		}
	}
	
	protected Object setField(StoneObject obj, Dot expr, Object rvalue) {
		String name = expr.name();
		try {
			obj.write(name, rvalue);
			return rvalue;
		} catch (AccessException e) {
			throw new StoneException("bad member access " + location() + ": " + name);
		}
	}

	protected Object setField(OptStoneObject obj, Dot expr, Object rvalue) {
		String name = expr.name();
		try {
			obj.write(name, rvalue);
			return rvalue;
		} catch (stone.OptStoneObject.AccessException e) {
			throw new StoneException("bad member access" + name, this);
		}
	}

	public void lookup(Symbols symbols) {
		ASTree left = left();
		if ("=".equals(operator())) {
			if (left instanceof Name) {
				((Name)left).lookupForAssign(symbols);
				right().lookup(symbols);
				return;
			}
		}
		left.lookup(symbols);
		right().lookup(symbols);
	}
	
}
